package lan.battletank.main;

import lan.battletank.R;
import lan.battletank.element.PlayerBulletEntity;
import lan.battletank.element.ControlPanelEntity;
import lan.battletank.element.EnemyBulletEntity;
import lan.battletank.element.EnemyEntity;
import lan.battletank.element.FpsEntity;
import lan.battletank.element.IndicatorEntity;
import lan.battletank.element.TankEntity;
import lan.battletank.environment.EnvConst;
import lan.battletank.environment.Stage;
import lan.battletank.logic.ElementStatusLogic;
import lan.battletank.logic.EnemyLogic;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

public class GameThread extends Thread {

    private final float ZOOM_MAX_RATIO = 2.0f; // Zoom max ratio
    private final float ZOOM_CHANGE_RATE = 0.05f; // in zooming effect
    private final float ZOOM_FOCUS_SPEED = 20f; // how fast the camera will move to tank

    private final int CAMERA_OVERALL = 0;
    private final int CAMERA_ZOOMING = 1;
    private final int CAMERA_PANNING = 2;

    private SurfaceHolder mSurfaceHolder;
    private Handler mHandler;
    private Context mContext;

    /** The drawable to use as the background of the animation canvas */
    private Bitmap mBackgroundImage;

    /** Current width and height of the surface/canvas. */
    private int mCanvasWidth = 1;
    private int mCanvasHeight = 1;

    /** Coordinates of tap action happened. */
    private float mTapInX;
    private float mTapInY;

    /** When in panning mode, used for maintaining the current zoom pivot coordinates. */
    private float mCurrentPanningX;
    private float mCurrentPanningY;

    /** How much distance to pan when fling happens. */
    private float mPanDistanceX;
    private float mPanDistanceY;

    /** Actual Zoom pivot coordinates */
    private float mZoomFromX;
    private float mZoomFromY;
    private float mCurrentZoomRatio;
    private int mCameraState;
    private boolean mTapIn;
    private boolean mIsChangingZoom;
    private boolean mPanning;
    private boolean mIsFocusing;

    Stage mStage;
    TankEntity mPlayer;
    PlayerBulletEntity mBullet;
    ControlPanelEntity mControlPanel;
    EnemyEntity mEnemy;
    EnemyBulletEntity mEnemyBullet;
    IndicatorEntity mIndicator;
    FpsEntity mFps;

    /** Indicate whether the surface has been created & is ready to draw */
    private boolean mRun = false;

    /** Used to figure out elapsed time between frames */
    private long mLastTime;

    public GameThread(SurfaceHolder surfaceHolder, Context context, Handler handler) {
        mSurfaceHolder = surfaceHolder;
        mHandler = handler;
        mContext = context;
        Resources res = mContext.getResources();
        // load background image as a Bitmap instead of a Drawable b/c
        // we don't need to transform it and it's faster to draw this way
        mBackgroundImage = BitmapFactory.decodeResource(res, R.drawable.background_sky);

        mPlayer = new TankEntity(context);
        mBullet = new PlayerBulletEntity(context);
        mControlPanel = new ControlPanelEntity(context);
        mEnemy = new EnemyEntity(context);
        mEnemyBullet = new EnemyBulletEntity(context);
        mIndicator = new IndicatorEntity(context);
        mFps = new FpsEntity();

        mZoomFromX = 0;
        mZoomFromY = 0;
        mCurrentZoomRatio = 1;
        mCameraState = CAMERA_OVERALL;
        mCurrentPanningX = 0;
        mCurrentPanningY = 0;
        mTapIn = false;
        mIsChangingZoom = false;
        mPanning = false;
        mIsFocusing = false;

        EnemyLogic.inAction = false;

        GameStatus.currentTurn = 1;
        GameStatus.setBattleStatus(EnvConst.BATTLE_PLAYER_TURN, mIndicator);
        GameStatus.setGameStatus(EnvConst.GAME_IN_BATTLE, mIndicator);
    }

    @Override
    public void run() {
        while (mRun) {
            Canvas c = null;
            try {
                c = mSurfaceHolder.lockCanvas(null);
                synchronized (mSurfaceHolder) {
                    if (GameStatus.getGameStatus() == EnvConst.GAME_IN_BATTLE) {
                        updateActions();
                    }
                    if (GameStatus.getGameStatus() != EnvConst.GAME_PAUSE) {
                        doDraw(c);
                    }
                }
            } finally {
                // do this in a finally so that if an exception is thrown
                // during the above, we don't leave the Surface in an
                // inconsistent state
                if (c != null) {
                    mSurfaceHolder.unlockCanvasAndPost(c);
                }
            }
            mFps.updateFps(); // update the FPS
        }
    }

    /**
     * Pauses the physics update & animation.
     */
    public void pause() {
        synchronized (mSurfaceHolder) {
            if (GameStatus.getGameStatus() == EnvConst.GAME_IN_BATTLE) {
                setState(EnvConst.GAME_PAUSE);
            }
        }
    }

    /**
     * @param b true to run, false to shut down
     */
    public void setRunning(boolean b) {
        mRun = b;
    }

    /**
     * @param mode Game Status
     */
    public void setState(int mode) {
        synchronized (mSurfaceHolder) {
            GameStatus.setGameStatus(mode, mIndicator);
        }
    }

    /* Callback invoked when the surface dimensions change. */
    public void setSurfaceSize(int width, int height) {
        // synchronized to make sure these all change atomically
        synchronized (mSurfaceHolder) {
            mCanvasWidth = width;
            mCanvasHeight = height;
            mStage = new Stage(mContext, mCanvasWidth, mCanvasHeight);
            mPlayer.setPositionAndInitialize(mStage, mCanvasWidth, mCanvasHeight);
            mControlPanel.setPositionAndInitialize(mCanvasWidth, mCanvasHeight);
            mEnemy.setPositionAndInitialize(mStage, mCanvasWidth, mCanvasHeight);
            mIndicator.setPositionAndInitialize(mCanvasWidth, mCanvasHeight);
            mFps.setPositionAndInitialize(mCanvasWidth, mCanvasHeight);
            // don't forget to resize the background image
            mBackgroundImage = Bitmap.createScaledBitmap(mBackgroundImage, width, height, true);
        }
    }

    /**
     */
    private void doDraw(Canvas canvas) {
        // Draw the background image. Operations on the Canvas accumulate
        // so this is like clearing the screen.
        canvas.save();
        if (mIsChangingZoom || mIsFocusing || mCameraState == CAMERA_ZOOMING || mCameraState == CAMERA_PANNING) {
            canvas.scale(mCurrentZoomRatio, mCurrentZoomRatio, mZoomFromX, mZoomFromY);
        }
        canvas.drawBitmap(mBackgroundImage, 0, 0, null);
        mStage.draw(canvas);
        mPlayer.draw(canvas);
        mEnemy.draw(canvas);
        if (mPlayer.isFiring()) {
            mBullet.draw(canvas);
        }
        if (mEnemy.isFiring()) {
            mEnemyBullet.draw(canvas);
        }
        canvas.restore();
        mControlPanel.draw(canvas);
        mIndicator.draw(canvas);
        mFps.draw(canvas);
    }

    /**
     */
    private void updateActions() {
        long now = System.currentTimeMillis();
        double elapsed = (now - mLastTime) / 1000.0;

        mPlayer.action(mControlPanel, mEnemy, mStage, mCanvasWidth, mCanvasHeight, elapsed);
        mBullet.action(mPlayer, mEnemy, mStage, mIndicator, mCanvasWidth, mCanvasHeight, elapsed);
        mEnemy.action(mControlPanel, mPlayer, mStage, mCanvasWidth, mCanvasHeight, elapsed);
        mEnemyBullet.action(mEnemy, mPlayer, mStage, mIndicator, mCanvasWidth, mCanvasHeight, elapsed);
        mIndicator.update(elapsed);
        if (!mPlayer.isFiring() && !mEnemy.isFiring()) {
            GameStatus.checkGameStatus(mPlayer, mEnemy, mEnemyBullet, mIndicator);
        }
        mLastTime = now;
        if (mIsChangingZoom || mIsFocusing || mCameraState == CAMERA_ZOOMING || mCameraState == CAMERA_PANNING) {
            setCameraCoordinates(mPlayer, mBullet);
        }
    }

    /**
     * @param event
     * @return
     */
    public boolean onTouchEvent(MotionEvent event) {
        if (GameStatus.getGameStatus() != EnvConst.GAME_IN_BATTLE && event.getAction() == MotionEvent.ACTION_UP) {
            startNewRound();
            return true;
        }
        if (GameStatus.getBattleStatus() != EnvConst.BATTLE_PLAYER_TURN) {
            return true;
        }

        float touchedX, touchedY;
        int action = event.getAction();
        touchedX = event.getX();
        touchedY = event.getY();
        switch (action) {
        case MotionEvent.ACTION_DOWN:
            if (GameStatus.getGameStatus() == EnvConst.GAME_IN_BATTLE) {
                if (mControlPanel.touched(mPlayer, touchedX, touchedY)) {
                    if (mCameraState == CAMERA_PANNING || mCameraState == CAMERA_ZOOMING) {
                        mIsFocusing = true;
                        mCurrentPanningX = mZoomFromX;
                        mCurrentPanningY = mZoomFromY;
                    }
                    ElementStatusLogic.setStatus(mPlayer, mBullet, mControlPanel, mIndicator);
                    mLastTime = System.currentTimeMillis();
                } else {
                    mTapIn = true;
                    mTapInX = touchedX;
                    mTapInY = touchedY;
                    if (mCameraState == CAMERA_PANNING) {
                        mPanDistanceX = 0;
                        mPanDistanceY = 0;
                        mCurrentPanningX = mZoomFromX;
                        mCurrentPanningY = mZoomFromY;
                    } else {
                        mCurrentPanningX = (float) mPlayer.centerX;
                        mCurrentPanningY = (float) mPlayer.centerY;
                    }
                }
            }
            break;
        case MotionEvent.ACTION_MOVE:
            if (mTapIn && (mCameraState == CAMERA_ZOOMING || mCameraState == CAMERA_PANNING)) {
                mCameraState = CAMERA_PANNING;
                mPanning = true;
                mPanDistanceX = touchedX - mTapInX;
                mPanDistanceY = touchedY - mTapInY;
            }
            if (GameStatus.getGameStatus() == EnvConst.GAME_IN_BATTLE) {
                if (!mControlPanel.maintainTouched(touchedX, touchedY)) {
                    ElementStatusLogic.setStatus(mPlayer, mBullet, mControlPanel, mIndicator);
                }
            }
            break;
        case MotionEvent.ACTION_UP:
            if (GameStatus.getGameStatus() == EnvConst.GAME_IN_BATTLE) {
                mControlPanel.reset();
                mLastTime = System.currentTimeMillis();
                ElementStatusLogic.setStatus(mPlayer, mBullet, mControlPanel, mIndicator);
                if (mTapIn && !mPanning) {
                    if (mCameraState == CAMERA_OVERALL) {
                        mCameraState = CAMERA_ZOOMING;
                    } else {
                        mCameraState = CAMERA_OVERALL;
                    }
                    mIsChangingZoom = true;
                } else if (mCameraState == CAMERA_PANNING) {
                    mPanning = false;
                }
                mTapIn = false;
            }
            break;
        default:
            break;
        }
        return true;
    }

    private void startNewRound() {
        mStage.initStage();
        mPlayer.setPositionAndInitialize(mStage, mCanvasWidth, mCanvasHeight);
        mEnemy.setPositionAndInitialize(mStage, mCanvasWidth, mCanvasHeight);
        GameStatus.setGameStatus(EnvConst.GAME_IN_BATTLE, mIndicator);
        GameStatus.setBattleStatus(EnvConst.BATTLE_PLAYER_TURN, mIndicator);
        mCameraState = CAMERA_OVERALL;
    }

    private void setCameraCoordinates(TankEntity tank, PlayerBulletEntity bullet) {
        if (mIsFocusing) {
            if (mCurrentPanningX - mPlayer.centerX > ZOOM_FOCUS_SPEED) {
                mCurrentPanningX -= ZOOM_FOCUS_SPEED;
            } else if (mCurrentPanningX - mPlayer.centerX < -ZOOM_FOCUS_SPEED) {
                mCurrentPanningX += ZOOM_FOCUS_SPEED;
            } else {
                mCurrentPanningX = mPlayer.centerX;
            }
            if (mCurrentPanningY - mPlayer.centerY > ZOOM_FOCUS_SPEED) {
                mCurrentPanningY -= ZOOM_FOCUS_SPEED;
            } else if (mCurrentPanningY - mPlayer.centerY < -ZOOM_FOCUS_SPEED) {
                mCurrentPanningY += ZOOM_FOCUS_SPEED;
            } else {
                mCurrentPanningY = mPlayer.centerY;
            }
            mZoomFromX = mCurrentPanningX;
            mZoomFromY = mCurrentPanningY;
            if (mZoomFromX == mPlayer.centerX && mZoomFromY == mPlayer.centerY) {
                mIsFocusing = false;
                mCameraState = CAMERA_ZOOMING;
            }
        } else if (mCameraState == CAMERA_PANNING) {
            mZoomFromX = mCurrentPanningX - mPanDistanceX;
            mZoomFromY = mCurrentPanningY - mPanDistanceY;
        } else if (tank.isFiring()) {
            mZoomFromX = (float) bullet.centerX;
            mZoomFromY = (float) bullet.centerY;
        } else {
            mZoomFromX = (float) tank.centerX;
            mZoomFromY = (float) tank.centerY;
            mCurrentPanningX = mZoomFromX;
            mCurrentPanningY = mZoomFromY;
        }
        if (mZoomFromX > mCanvasWidth) {
            mZoomFromX = mCanvasWidth;
        } else if (mZoomFromX < 0) {
            mZoomFromX = 0;
        }
        if (mZoomFromY < 0) {
            mZoomFromY = 0;
        } else if (mZoomFromY > mCanvasHeight) {
            mZoomFromY = mCanvasHeight;
        }
        if (mCameraState == CAMERA_ZOOMING && mIsChangingZoom) {
            mCurrentZoomRatio += ZOOM_CHANGE_RATE;
            if (mCurrentZoomRatio > ZOOM_MAX_RATIO) {
                mCurrentZoomRatio = ZOOM_MAX_RATIO;
                mIsChangingZoom = false;
            }
        } else if (mCameraState == CAMERA_OVERALL && mIsChangingZoom) {
            mCurrentZoomRatio -= ZOOM_CHANGE_RATE;
            if (mCurrentZoomRatio < 1) {
                mCurrentZoomRatio = 1;
                mIsChangingZoom = false;
            }
        }
    }
}
